package com.toonly.tests;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 1. List
 * 2. setDefaultValue
 * 3. multi setters which have a same name
 */
public class ReflectTest {

    private static Map<String, Class<?>> typeVariables = new HashMap<>();
    private static Set<Class<?>> objClasses = new HashSet<>();

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, JsonProcessingException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {

//        Object o = construct(SupplierProduct.class);
//        Object o = construct(PageResult.class, "T", SupplierCustomerList.class);
        Object o = new Object();

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        System.out.println(objectMapper.writeValueAsString(fillValues(o)));
    }

    private static Object fillValues(Object o) throws IllegalAccessException, ClassNotFoundException, InstantiationException, InvocationTargetException {

        Class<?> theClass = o.getClass();
        if (objClasses.contains(theClass)) {
            return o;
        }
        objClasses.add(theClass);

        Field[] declaredFields = theClass.getDeclaredFields();

        for (Field field : declaredFields) {
            System.out.println(field.getName() + " : " + field.getGenericType());

            if ((field.getModifiers() & 1 << 4) != 0) {
                continue;
            }

            field.setAccessible(true);

            Class<?> aClass = field.getType();
            field.set(o, getDefaultValue(aClass, field.getGenericType()));
        }

        objClasses.remove(theClass);
        return o;
    }

    private static Object getDefaultValue(Class<?> aClass, Type type) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
        if (isGenericType(type)) {
            aClass = typeVariables.get(type.toString());
        }

        if (aClass == Boolean.class || aClass == boolean.class) {
            return true;
        } else if (aClass == Integer.class || aClass == Short.class || aClass == int.class || aClass == short.class) {
            return 1;
        } else if (aClass == Long.class || aClass == long.class) {
            return 0L;
        } else if (aClass == Float.class || aClass == Double.class || aClass == float.class || aClass == double.class) {
            return 0.0;
        } else if (aClass == String.class) {
            return "string";
        } else if (aClass == Date.class) {
            return new Date();
        } else if (aClass == BigDecimal.class) {
            return new BigDecimal("0.0");
        } else if (aClass == BigDecimal[].class) {
            return new BigDecimal[]{new BigDecimal("0.0")};
        } else if (aClass == List.class) {
            ParameterizedType pt = (ParameterizedType) type;
            Type genericType = pt.getActualTypeArguments()[0];
            String genericTypeName = genericType.toString();
            if (isGenericType(genericType)) {
                return Collections.singletonList(getDefaultValue(typeVariables.get(genericTypeName), genericType));
            }
            return Collections.singletonList(getDefaultValue(Class.forName(genericTypeName.substring("class ".length())), type));
        } else if (aClass.isEnum()) {
            return aClass.getEnumConstants()[0];
        } else {
            return fillValues(construct(aClass));
        }
    }

    private static boolean isGenericType(Type type) {
        return typeVariables.keySet().contains(type.toString());
    }

    private static Object construct(Class<?> clazz, Object... typeVars) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
        if (typeVars.length > 0) {
            constructGenericTypes(typeVars);
        }

        Constructor<?>[] constructors = clazz.getConstructors();

        if (constructors.length == 0) {
            System.out.println(clazz);
            return clazz.newInstance();
        }
        System.out.println(constructors[0]);

        Constructor<?> constructor = constructors[0];
        Class<?>[] parameterTypes = constructor.getParameterTypes();
        Type[] genericParameterTypes = constructor.getGenericParameterTypes();

        List<Object> params = new ArrayList<>();
        for (int i = 0; i < parameterTypes.length; i++) {
            params.add(getDefaultValue(parameterTypes[i], genericParameterTypes[i]));
        }

        if (parameterTypes.length > 0) {
            return constructor.newInstance(params);
        } else {
            return constructor.newInstance();
        }
    }

    private static void constructGenericTypes(Object[] typeVars) {
        if (typeVars.length % 2 != 0) {
            throw new RuntimeException("wrong type variables configs");
        }

        for (int i = 0; i < typeVars.length; i += 2) {
            typeVariables.put(typeVars[i].toString(), (Class<?>) typeVars[i + 1]);
        }
    }

}
